/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.muni.fi.diplomka.datalayer.daoimpl;

import cz.fi.muni.diplomka.common.Coordinates;
import cz.muni.fi.diplomka.datalayer.dao.CityDao;
import cz.muni.fi.diplomka.datalayer.entity.CityEntity;
import cz.muni.fi.diplomka.datalayer.util.DbConnector;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.neo4j.cypher.javacompat.ExecutionEngine;
import org.neo4j.cypher.javacompat.ExecutionResult;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.ResourceIterator;
import org.neo4j.graphdb.Transaction;

/**
 *
 * @author Jakub Kutil
 */
public class CityDaoImpl implements CityDao {

    @Override
    public void addCity(CityEntity cityEntity) {
        if (cityEntity == null) {
            throw new IllegalArgumentException("City Entity can not be null");
        }

        GraphDatabaseService gds = DbConnector.getDBConnection();
        ExecutionEngine engine = DbConnector.getEngine();


        try (Transaction t = gds.beginTx()) {

            Map<String, Object> params = new HashMap<>();

            String query = "MATCH (City:City) RETURN max(City.id)";

            Long key = new Long(0);
            ExecutionResult result = engine.execute(query);
            Iterator<Long> number = result.columnAs("max(City.id)");
            while (number.hasNext()) {
                key = number.next();
            }

            if (key == null) {
                key = new Long(0);
            }
            cityEntity.setId(new Long(key + 1));

            params.put("id", cityEntity.getId());
            params.put("name", cityEntity.getName());
            params.put("playerId", cityEntity.getPlayerId());
            params.put("xCoordinate", cityEntity.getCoordinates().getX_coordinate());
            params.put("yCoordinate", cityEntity.getCoordinates().getY_coordinate());

            query = "CREATE (City: City {id : {id}, name : {name}, xCoordinate : {xCoordinate}, yCoordinate : {yCoordinate}})";
            engine.execute(query, params);

            query = "MATCH (Player:Player {id : {playerId}}),(City:City {id : {id}})"
                    + "CREATE (Player)-[r:OWNS]->(City)";
            engine.execute(query, params);
            t.success();
        }
    }

    @Override
    public void editCity(CityEntity cityEntity) {
        if (cityEntity == null) {
            throw new IllegalArgumentException("City Entity can not be null");
        }

        GraphDatabaseService gds = DbConnector.getDBConnection();
        ExecutionEngine engine = DbConnector.getEngine();

        try (Transaction t = gds.beginTx()) {

            Map<String, Object> params = new HashMap<>();

            params.put("id", cityEntity.getId());
            params.put("name", cityEntity.getName());

            String query = "MATCH (City:City { id : {id} }) SET City.name = {name}";
            engine.execute(query, params);
            t.success();
        }
    }

    @Override
    public CityEntity getCityAtPosition(Coordinates coordinates) {
        if (coordinates == null) {
            throw new IllegalArgumentException("Coordinates can not be null");
        }

        CityEntity cityEntity = new CityEntity();
        GraphDatabaseService gds = DbConnector.getDBConnection();
        ExecutionEngine engine = DbConnector.getEngine();


        try (Transaction t = gds.beginTx()) {
            Map<String, Object> params = new HashMap<>();

            params.put("xCoordinate", coordinates.getX_coordinate());
            params.put("yCoordinate", coordinates.getY_coordinate());

            String query = "MATCH (City:City { xCoordinate: {xCoordinate}, yCoordinate: {yCoordinate}})"
                    + "<-[r:OWNS]-(Player:Player) "
                    + "RETURN City, Player.id";
            ExecutionResult result = engine.execute(query, params);
            ResourceIterator<Map<String, Object>> cities = result.iterator();

            while (cities.hasNext()) {
                Map<String, Object> row = cities.next();
                Node node = (Node) row.get("City");

                cityEntity.setId(new Long(node.getProperty("id").toString()));
                cityEntity.setName(node.getProperty("name").toString());
                cityEntity.setPlayerId(Long.valueOf(row.get("Player.id").toString()));

                Coordinates c = new Coordinates();
                c.setX_coordinate(Integer.valueOf(node.getProperty("xCoordinate").toString()));
                c.setY_coordinate(Integer.valueOf(node.getProperty("yCoordinate").toString()));
                cityEntity.setCoordinates(c);
            }
            t.success();
        }
        return cityEntity;
    }

    @Override
    public CityEntity getCityById(Long id) {
        if (id == null) {
            throw new IllegalArgumentException("Id can not be null");
        }
        CityEntity cityEntity = new CityEntity();
        GraphDatabaseService gds = DbConnector.getDBConnection();
        ExecutionEngine engine = DbConnector.getEngine();

        try (Transaction t = gds.beginTx()) {
            Map<String, Object> params = new HashMap<>();

            params.put("id", id);

            String query = "MATCH (City:City { id: {id} })<-[r:OWNS]-(Player:Player) RETURN City, Player.id";
            ExecutionResult result = engine.execute(query, params);
            ResourceIterator<Map<String, Object>> cities = result.iterator();

            while (cities.hasNext()) {
                Map<String, Object> row = cities.next();
                Node node = (Node) row.get("City");

                cityEntity.setId(new Long(node.getProperty("id").toString()));
                cityEntity.setName(node.getProperty("name").toString());
                cityEntity.setPlayerId(Long.valueOf(row.get("Player.id").toString()));

                Coordinates c = new Coordinates();
                c.setX_coordinate(Integer.valueOf(node.getProperty("xCoordinate").toString()));
                c.setY_coordinate(Integer.valueOf(node.getProperty("yCoordinate").toString()));
                cityEntity.setCoordinates(c);

            }
            t.success();
        }
        return cityEntity;
    }

    @Override
    public CityEntity getCityByName(String name) {
        if (name == null) {
            throw new IllegalArgumentException("Name can not be null");
        }
        CityEntity cityEntity = new CityEntity();
        GraphDatabaseService gds = DbConnector.getDBConnection();
        ExecutionEngine engine = DbConnector.getEngine();


        try (Transaction t = gds.beginTx()) {
            Map<String, Object> params = new HashMap<>();

            params.put("name", name);

            String query = "MATCH (City:City { name: {name} })<-[r:OWNS]-(Player:Player) RETURN City, Player.id";
            ExecutionResult result = engine.execute(query, params);
            ResourceIterator<Map<String, Object>> cities = result.iterator();

            while (cities.hasNext()) {
                Map<String, Object> row = cities.next();
                Node node = (Node) row.get("City");

                cityEntity.setId(new Long(node.getProperty("id").toString()));
                cityEntity.setName(node.getProperty("name").toString());
                cityEntity.setPlayerId(Long.valueOf(row.get("Player.id").toString()));

                Coordinates c = new Coordinates();
                c.setX_coordinate(Integer.valueOf(node.getProperty("xCoordinate").toString()));
                c.setY_coordinate(Integer.valueOf(node.getProperty("yCoordinate").toString()));
                cityEntity.setCoordinates(c);
            }
            t.success();
        }
        return cityEntity;
    }

    @Override
    public List<CityEntity> getCitiesOfPlayer(Long playerId) {
        if (playerId == null) {
            throw new IllegalArgumentException("Player Id can not be null");
        }

        List<CityEntity> cityList = new ArrayList<>();
        GraphDatabaseService gds = DbConnector.getDBConnection();
        ExecutionEngine engine = DbConnector.getEngine();


        try (Transaction t = gds.beginTx()) {

            Map<String, Object> params = new HashMap<>();

            params.put("playerId", playerId);

            String query = "MATCH (Player:Player {id : {playerId}})-[r:OWNS]->(City:City) "
                    + "RETURN City";
            ExecutionResult result = engine.execute(query, params);
            Iterator<Node> cities = result.columnAs("City");
            while (cities.hasNext()) {
                Node node = cities.next();
                CityEntity cityEntity = new CityEntity();
                cityEntity.setId(new Long(node.getProperty("id").toString()));
                cityEntity.setName(node.getProperty("name").toString());
                cityEntity.setPlayerId(playerId);
                Coordinates c = new Coordinates();
                c.setX_coordinate(Integer.valueOf(node.getProperty("xCoordinate").toString()));
                c.setY_coordinate(Integer.valueOf(node.getProperty("yCoordinate").toString()));
                cityEntity.setCoordinates(c);

                cityList.add(cityEntity);
            }
            t.success();
        }
        return cityList;
    }

    @Override
    public List<CityEntity> getAllCities() {
        List<CityEntity> cityList = new ArrayList<>();

        GraphDatabaseService gds = DbConnector.getDBConnection();
        ExecutionEngine engine = DbConnector.getEngine();


        try (Transaction t = gds.beginTx()) {

            String query = "MATCH (Player:Player)-[r:OWNS]->(City:City) "
                    + "RETURN City, Player.id";
            ExecutionResult result = engine.execute(query);

            ResourceIterator<Map<String, Object>> cities = result.iterator();

            while (cities.hasNext()) {
                Map<String, Object> row = cities.next();
                Node node = (Node) row.get("City");

                CityEntity cityEntity = new CityEntity();

                cityEntity.setId(new Long(node.getProperty("id").toString()));
                cityEntity.setName(node.getProperty("name").toString());
                cityEntity.setPlayerId(new Long(row.get("Player.id").toString()));

                Coordinates c = new Coordinates();
                c.setX_coordinate(Integer.valueOf(node.getProperty("xCoordinate").toString()));
                c.setY_coordinate(Integer.valueOf(node.getProperty("yCoordinate").toString()));
                cityEntity.setCoordinates(c);
                cityList.add(cityEntity);

            }
            t.success();
        }
        return cityList;
    }
}
